// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js
// @output_file_name default.js
// ==/ClosureCompiler==

/**
 * Queensland/Australia 2011 Flood map.
 * @author James McGill (jmcgill@google.com)
 */

var firstLoaded = true;

var DATA_REFRESH_MS = 5 * 60 * 1000;

  // KML Layers to automatically add. Each layer should be a 4-tupple of:
  // [Name, URL, Attribution Text, Attribution Link]

  var kmlGroups = [
      {
        name: 'Evacuation Centres',
        layers: ['qldevac', 'vicevac']
      }, {
        name: 'Road Closures',
        layers: ['qldroadclosures', 'vicroadclosures']
      }, {
        name: 'SES Alerts',
        layers: ['vicses', 'nswses']
      }, {
        name: 'Power Outages',
        layers: ['qldelec', 'vicelecpowercor']
      }, {
        name: 'Photos from the Brisbane Floods (Yellow)',
        layers: ['brisphotos']
      }
  ];

  var kml = [
    ["Queensland Evacuation Centres",
     "http://mapvisage.appspot.com/static/floodmap/evaccentre.kml",
     "Evacuation Centres (ABC, BCC, Qld Police)",
     "",
     "kml",
     "qldevac"
    ],
    ["Queensland Road Closures (Pink)",
     "http://mapvisage.appspot.com/kml/qldroadclosures",
     "Queensland Road Closures, Queensland Government",
     "http://highload.131940.qld.gov.au/",
     "kml",
     "qldroadclosures"
    ],
    ["Victorian Evacuation Centres",
     "http://mapvisage.appspot.com/kml/vicevac",
     "Victorian Evacuation Centres (DHS, ABC)",
     "http://www.abc.net.au/local/stories/2011/01/14/3113287.htm?site=melbourne",
     "kml",
     "vicevac"
    ],
    ["Victorian Road Closures",
     "http://mapvisage.appspot.com/kml/vicroadclosures",
     "Victorian Road Closures, VicRoads",
     "http://www.vicroads.vic.gov.au",
     "kml",
     "vicroadclosures"
    ],
    ["Queensland Power Outages",
     "http://mapvisage.appspot.com/electricity/json9",
     "Queensland Power Outages (Energex)",
     "http://www.energex.com.au/network/asp/unplanned_interruptions.asp",
     "kml",
     "qldelec"
    ],
    ["Affected towns and cities (ABC)",
     "http://maps.google.com/maps/ms?ie=UTF8&msa=0&output=kml&msid=210606325855659952999.000498e8d8ebf782c3d80",
     "Affected towns and cities, ABC",
     "http://www.abc.net.au",
     "kml",
     "abcaffected"
    ],
    ["Worst affected areas (SBS)",
     "http://maps.google.com/maps/ms?ie=UTF8&msa=0&output=kml&msid=216305641036137584677.000498fa830661a4cbafb",
     "Worst affected areas, SBS",
     "http://www.sbs.com.au",
     "kml",
     "sbsaffected"
    ],
    ["Photos from the Brisbane Floods (Yellow)",
     "http://mapvisage.appspot.com/static/floodmap/brisphotos.kmz",
     "Photos from the Queensland Floods (Above Photography)",
     "http://www.abovephotography.com.au",
     "kml",
     "brisphotos"
    ],
    ["Western Victorian Power Outages",
     "http://mapvisage.appspot.com/kml/vicelec",
     "Victorian Power Outages (Powercor)",
     "http://www.powercor.com.au/Power_Outages/Powercor_Outages/Powercor_-_Network_Wide/",
     "kml",
     "vicelecpowercor"
    ],
    ["Victorian SES Alerts",
     "http://mapvisage.appspot.com/kml/vicses",
     "Victorian SES Flood Alerts",
     "http://www.ses.vic.gov.au",
     "kml",
     'vicses'
    ],
    ["NSW Alerts",
     "http://mapvisage.appspot.com/kml/nswses",
     "NSW Flood Alerts",
     "http://www.bom.gov.au/nsw",
     "kml",
     'nswses'
    ]
];

  var map;
  var layers = {};
  var layerMap = {};
  var iw = new google.maps.InfoWindow();
  var SEARCH_PROMPT = "Search";
  var riverMarkers = [];
  var EMPTY_TILE_URL = 'http://maps.gstatic.com/mapfiles/qldfloods/n.png';

  for (var i = 0; i < kml.length; ++i) {
    var spec = kml[i];
    layerMap[spec[5]] = i;
  }
  function getUrlParams() {
    var params = [];
    // Get current center and zoom so that we do not reset the map on
    // refresh.
    var center = map.getCenter();
    if (center) params.push('center=' + center.lat() + ',' + center.lng());
    var zoom = map.getZoom();
    if (zoom != null) params.push('zoom=' + map.getZoom());
    var layersValues = [];
    for (var layerName in layers) {
      if (layers[layerName] && layers[layerName].getMap && layers[layerName].getMap()) {
        layersValues.push(layerName);
      }
    }
    params.push('layers=' +  layersValues.join(','));
    return params.join('&');
  }

  function parseUrlParams() {
    var url = window.location.href;
    // Uses the hash parameters if present.  Otherwise looks for parameters
    // following the ?.  If both # and ? are present, use the parameters
    // following #.
    var hash = url.indexOf('#');
    if (hash != -1) {
      url = url.substring(hash + 1);
    } else {
      url = url.substring(url.indexOf('?') + 1);
    }
    var paramPairs = url.split('&');
    var params = {};
    for (var i = 0, I = paramPairs.length; i < I; ++i) {
      var paramSplit = paramPairs[i].split('=');
      var paramName = paramSplit[0];
      var paramValue = true;
      if (paramSplit.length == 2) {
        paramValue = paramSplit[1];
      }
      params[paramName] = paramValue;
    }

    return params;
  }

  function parseCenter(center) {
    if (center) {
      var latLngString = center.split(',');
      if (latLngString.length == 2) {
        var lat = parseFloat(latLngString[0]);
        var lng = parseFloat(latLngString[1]);
        return new google.maps.LatLng(lat, lng);
      }
    }
    return null;
  }

  function getMapSize() {
    var map_inner = document.getElementById("map_canvas");
    return new google.maps.Size(map_inner.offsetWidth, map_inner.offsetHeight);
  }

  function initialize() {
    window.setTimeout(initialize2, 25);
  }

  function updateUrlHash() {
    window.location.hash = getUrlParams();
  }

  /**
   * Initialize the map.
   */
  function initialize2() {
    var myOptions = {
      disableDefaultUI: true,
      navigationControl: true,
      scaleControl: getMapSize().width > 400 ? true : false,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    }
    map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

    // Updates the window location hash when the map is done with a zoom or
    // center change.  Listens to idle to prevent changing the value during a
    // drag.
    google.maps.event.addListener(map, 'idle', updateUrlHash);

    var params = parseUrlParams();
    var myLatlng = parseCenter(params['center']);
    if (myLatlng) {
      var zoom = parseInt(params['zoom']);
      map.setCenter(myLatlng);
      map.setZoom(typeof zoom == 'number' ? zoom : 6);
    } else {
      // Fit to bounds of QLD if the center wasn't specified in the URL
      // parameters.
      var bounds = new google.maps.LatLngBounds(
          new google.maps.LatLng(-29, 137),
          new google.maps.LatLng(-16, 154));
      map.fitBounds(bounds);
    }

    var layersParam = params['layers'];
    var layersToShow;
    if (layersParam != null && layersParam != "") {
      layersToShow = layersParam.split(',');
    } else {
      layersToShow = ['Rivers', layerMap['qldroadclosures'], layerMap['vicroadclosures'], layerMap['brisphotos']];
    }

    // Add our custom UI.
    addLegend();
    addAttribution();
    addViewButton(layersToShow);
    // addMarginsButton();
    addSearchBar();

    //createRiverMarkerImages();
    //loadRiverLevels();

    // Add tiled overlay.
    initTileOverlay('Future', 'http://maps.gstatic.com/mapfiles/qldfloods/bcc-projected-20110113/', '/', false, -27.7624544568, 152.449862619, -27.3016916566, 153.213490071, 6, 15, [-27.471311398711347, 153.02791748804933, 11, 'Brisbane'], true);
    initTileOverlay('B', 'http://mw1.google.com/crisisresponse/2011/australia/flood/geoeye/BundabergAustralia_40cm-2011-01-13_maptiles/', '_', true, -25.062906723828544, 152.24217224121097, -24.767104762792147, 152.45640563964847, 12, 20, [-24.867454829905377,152.34453312695314, 13, 'Bundaberg'], false);
    initTileOverlay('Echuca', 'http://maps.gstatic.com/mapfiles/qldfloods/echuca-20110116/', '/', false, -36.1584407575, 144.712945092, -36.1049591171, 144.756658205, 6, 17, [-36.1316999373, 144.73480164850002, 12, 'Echuca'], true);
    initLayers();

    for (var i = 0; i < layersToShow.length; ++i) {
      var name = layersToShow[i];
      var layer = layers[name];
      if (layer) addLayer(name, layer);
    }

    // All loaded.
    firstLoaded = false;
  }

  function initTileOverlay(layerName, prefix, sep, geoeye, lat1, lng1, lat2, lng2, zoom1, zoom2, zoomTo, invY) {
    var overlayBounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat1, lng1), new google.maps.LatLng(lat2, lng2));

    var overlayZoomMin = zoom1;
    var overlayZoomMax = zoom2;

    var tileMap = new google.maps.ImageMapType({
        opacity: geoeye ? 0.85 : 0.60,
        isPng: !geoeye,
        tileSize: new google.maps.Size(256, 256),
        getTileUrl: function(tile, zoomLevel) {
          var tileProj = map.getProjection();

          if ((zoomLevel > overlayZoomMax) || (zoomLevel < overlayZoomMin)) {
            return EMPTY_TILE_URL;
          } else {
            var y = invY ? ((1 << zoomLevel) - tile.y - 1) : tile.y;

            var point1 = new google.maps.Point(256 * (tile.x / (1 << (zoomLevel))), 256 * ((tile.y + 1) / (1 << (zoomLevel))));
            var point2 = new google.maps.Point(256 * ((tile.x + 1) / ( 1 << (zoomLevel))), 256 * (tile.y / (1 << (zoomLevel))));

            var tileBounds = new google.maps.LatLngBounds(
                tileProj.fromPointToLatLng(point1, zoomLevel),
                tileProj.fromPointToLatLng(point2, zoomLevel));

            if (overlayBounds.intersects(tileBounds)) {
              if (geoeye)
                return prefix + tile.x + sep + y + sep + zoomLevel + '.jpg';
              else
                return prefix + zoomLevel + sep + tile.x + sep + y + '.png';
            } else {
              return EMPTY_TILE_URL;
            }
          }
        }});

    tileMap.setMap = function(my_map) {
      this.map_ = my_map;
      // Update the URL hash to reflect whether this layer is showing or
      // hidden.
      updateUrlHash();
      var overlayFound = false;

      for (var i = 0; i < map.overlayMapTypes.getLength(); ++i) {
        if (this == map.overlayMapTypes.getAt(i)) {
          overlayFound = i;
          break;
        }
      }

      if ((overlayFound !== false) && !my_map) {
        map.overlayMapTypes.removeAt(i);
      } else if ((overlayFound === false) && my_map) {
        map.overlayMapTypes.push(this);
      }

      if (zoomTo && my_map && !firstLoaded) {
        var shouldAsk = (map.getZoom() < (zoomTo[2] - 1)) || !overlayBounds.intersects(map.getBounds());
        var shouldZoom = false;
        if (shouldAsk)
          shouldZoom = confirm("Zoom in to " + zoomTo[3] + "?");

        if (shouldZoom) {
          map.setCenter(new google.maps.LatLng(zoomTo[0], zoomTo[1]));  // -24.867454829905377,152.34453312695314
          map.setZoom(zoomTo[2]);
        }
      }
    }
    tileMap.getMap = function() {
      return this.map_;
    };

    layers[layerName] = tileMap;
  }

  /**
   * Initializes all of the KML and Fusion Tables layers, adding a hash to the
   * end of the KML URLs to ensure it is refreshed every 5 minutes.
   */
  function initLayers() {
    document.getElementById("other_layers").innerHTML = '';

    /**
     * Adds the new layer to the layers map.  If a layer by that name already
     * existed, set the new layer's map to the old layer's and then remove the
     * old layer from the map.
     */
    function addLayerByName(name, newLayer) {
      var oldLayer = layers[name];
      if (oldLayer) {
        newLayer.setMap(oldLayer.getMap());
        oldLayer.setMap(null);
      }
      layers[name] = newLayer;

      google.maps.event.addListener(newLayer, 'map_changed', updateUrlHash);
    }

     // Current 5 minute block to avoid caching.
    var now = new Date();

    // Milliseconds since this line was written.
    var since = now.getTime() - 1294794959132;

    // 300000 milliseconds in 5 minutes. This gives a number which increases
    // once every 5 minutes.
    var block = Math.floor(since / DATA_REFRESH_MS);

    // NOTE(jmcgill): Now using KML instead of markers.
    var rivers = new google.maps.KmlLayer("http://mapvisage.appspot.com/flood/json9?" + block, {
        suppressInfoWindows: true,
        preserveViewport: true
    });
    addLayerByName('Rivers', rivers);
    google.maps.event.addListener(rivers, "click", showInfowindow);

    // Add all other kmls.
    for (var i = 0; i < kml.length; ++i) {
      var spec = kml[i];
      var escapeChar = spec[1].indexOf('?') == -1 ? '?' : '&';

      var doc = null;
      if (spec[4] == "kml") {
        doc = new google.maps.KmlLayer(spec[1] + escapeChar + block, {
          preserveViewport: true});
      } else if (spec[4] == "ft") {
        doc = new google.maps.FusionTablesLayer(spec[1]);
      }
      layerMap[spec[5]] = i;
      addLayerByName(i, doc);

      // Append to attributions.
      if (spec[2]) {
        document.getElementById("other_layers").innerHTML +=
          '<br><a href="' + spec[3] + '">' + spec[2] + '</a>';
      }
    }

    refreshLayers();
  }

  function refreshLayers() {
    window.setTimeout(initLayers, DATA_REFRESH_MS);
  }

  function requestUrl(url, callback) {
    var request;
    try {
      request = new ActiveXObject("MSXML2.XMLHTTP");
    } catch (NotIE5) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (NotIE6) {
        request = new XMLHttpRequest();
      }
    }

    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        // We expect a JSON object in return. Safe(ish) to eval it.
        eval('response = ' + request.responseText);
        callback(response);
      }
    }

    request.open("GET", url);
    request.send("");
  }

  var riverMarkerImages = {};
  function createRiverMarkerImages() {
    // Maps JSON flood level responses to specific markers.
    var markerMapping = {
      'Unknown': 'gray.gif',
      'Major': 'red.gif',
      'Moderate': 'orange.gif',
      'Below Minor': 'blue.gif',
      'Minor': 'green.gif'
    };

    for (var key in markerMapping) {
      riverMarkerImages[key] = new google.maps.MarkerImage(
        '/static/floodmap/images/rivers/' + markerMapping[key]);
    }
  }

  function loadRiverLevels() {
    requestUrl("/flood/json", parseRiverLevels);

    // Reload river levels every 5 minutes.
    window.setTimeout(loadRiverLevels, DATA_REFRESH_MS);
  }

  function parseRiverLevels(datum) {
    // Delete any existing markers.
    for (var i = 0; i < riverMarkers.length; ++i) {
      if (riverMarkers[i]) riverMarkers[i].setMap(null);
    }
    riverMarkers.length = 0;

    // Parse response, and create markers.
    for (var i = 0; i < datum.length; ++i) {
      var data = datum[i];
      var latLng = new google.maps.LatLng(data['lat'],
          data['long']);
      var markerIcon = riverMarkerImages[data['floodClass']];
      var marker = new google.maps.Marker({
        position: latLng,
        icon: markerIcon
      });
      marker.setMap(map);
      riverMarkers.push(marker);

      // Bind click behaviour to infowindow.
      google.maps.event.addListener(marker, 'click', function() {
        infowindow.setPosition(latLng);
        infowindow.setContent(renderRiverInfowindow(data));
      });
    }
  }

  /**
   * Render the contents of an infowindow for a river level
   * report. Maps JSON short names to English.
   */
  function renderRiverInfowindow(data) {
    var interestingProperties = {
      "height" : "River height (metres)",
      "floodClass" : "Flood class",
      "tendency": " Tendency",
      "time": "Time updated"
    };
    var details = "";
    for (var key in interestingProperties) {
      if (data[key]) {
        details += "<b>" + interestingProperties[key] +
          ": </b>" + data[key] + "<br>";
      }
    }
    return "<h3>" + data['name'] + "</h3><br>" + details;
  }

  /**
   * Show an infowindow at the clicked point. Supresses the previously open
   * infowindow, allowing us to show only one infowindow even though we have
   * multiple KML files.
   * @param {google.maps.KmlMouseEvent} e
   */
  function showInfowindow(e) {
    var parts = e.featureData.description.split(';');
    var iw_text = '<div style="font-family: Arial, sans-serif"><div style="font-size: 14px; font-weight: bold;">' +
        e.featureData.name + '</div><div style="font-size: 13px;">' +
        '<b>River Height:</b> ' + parts[0] + '<br>' +
        '<b>Tendency:</b> ' + parts[1] + '<br>' +
        '<b>Time:</b> ' + parts[2];

        // Only present in v > 9
        if (parts.length > 3) {
          iw_text += '<br><b>Flood class: </b>' + parts[3];
        }
        iw_text += '</div>';
    var opts = {
      position: e.latLng,
      pixelOffset: e.pixelOffset,
      content: iw_text
    };
    iw.setOptions(opts);
    iw.open(map);
  }

  /**
   * Single geocoder instance. Cached and re-used multiple times.
   * @type {Object}
   */
  var geocoder;

  /**
   * Add a geocoding search bar to the map.
   */
  function addSearchBar() {
    geocoder = new google.maps.Geocoder();

    // We need an outer div so we don't hit copyrights.
    var parentDiv = document.createElement('div');
    parentDiv.style.marginBottom = '25px';

    var div = document.createElement('div');
    div.className = 'searchouter';

    var button = document.createElement('input');
    button.type = 'button';
    button.value = 'Search';
    button.className = 'searchbutton';

    var input = document.createElement('input');
    input.className = 'searchbox';
    input.value = SEARCH_PROMPT;

    google.maps.event.addDomListener(input, 'keypress', function(e) {
      if (e.keyCode == 13) search(input.value);
    });

    // When we select the textbox, remove sample text.
    google.maps.event.addDomListener(input, 'focus', function() {
      if (input.value == SEARCH_PROMPT) input.value = "";
      input.style.color = "#000000";
    });

    google.maps.event.addDomListener(input, 'blur', function() {
      if (input.value == "") {
        input.value = SEARCH_PROMPT;
        input.style.color = "#777777";
      }
    });

    // On click, search.
    google.maps.event.addDomListener(button, 'click', function() {
        search(input.value);
    });

    // Add to map.
    div.appendChild(input);
    div.appendChild(button);
    if (getMapSize().width > 400) {
      parentDiv.appendChild(div);
      map.controls[google.maps.ControlPosition.BOTTOM].push(parentDiv);
    } else {
      parentDiv.className = 'controlparent';

      var button = createButton('Search', 70, true);
      parentDiv.appendChild(button);

      var dropDown = document.createElement('div');
      dropDown.style.position = 'absolute';
      dropDown.style.display = 'none';
      dropDown.style.left = '0px';
      dropDown.style.width = '275px';
      dropDown.appendChild(div);
      parentDiv.appendChild(dropDown);

      makeToggleButton(button, dropDown);

      map.controls[google.maps.ControlPosition.TOP_RIGHT].push(parentDiv);
    }
  }

  /**
   * Geocode the contents of the search bar, and center the map at that
   * location. Search terms are suffixed with Australia as a hint to the
   * Geocoder.
   * @param {string} text Text to search for.
   */
  function search(text) {
    if (text == SEARCH_PROMPT) return;
      if (geocoder) {
        geocoder.geocode({address: text + ", Australia"}, function(results, status) {
          if (status == google.maps.GeocoderStatus.OK) {
            map.fitBounds(results[0].geometry.viewport);
          } else {
            alert("The location you entered could not be found.");
          }
        });
      }
  }

  /**
   * Create a Google Maps style button.
   * @param {string} text The text to put on the button.
   * @param {number} width The width of the button in pixels.
   */
  function createButton(text, width, opt_dropdown) {
    var outerDiv = document.createElement('div');
    var innerDiv = document.createElement('div');
    outerDiv.className = 'control';
    innerDiv.className = 'control-inner';
    outerDiv.style['width'] = width + 'px';
    innerDiv.innerHTML = text;
    if (opt_dropdown) innerDiv.innerHTML += ' <img src="down-arrow.png">';
    outerDiv.appendChild(innerDiv);
    return outerDiv;
  }

  /**
   * Toggle a button. This updates the style of the button to make it look
   * pressed/unpressed.
   * @param {Node} button The button div.
   * @return {boolean} True if the button is pressed after being toggled.
   */
  function toggleButton(button) {
    var inner = button.childNodes[0];
    var selected = !button.selected_;  // Toggle selectedness.
    button.selected_ = selected;
    inner.className = selected ? 'selected-inner' : 'control-inner';
    return selected;
  }

  /**
   * The list of all toggle buttons controlling drop downs.
   * @type {Array.<Node>}
   */
  var dropDownToggleButtons_ = [];

  /**
   * Makes a toggle button which hides and shows a drop down.
   * @param {Node} button  The button element.
   * @param {Node} dropDown  The drop down to show or hide.
   */
  function makeToggleButton(button, dropDown) {
    // Listen to mouse events on the button.
    google.maps.event.addDomListener(button, 'click', function() {
      resetDropDownToggleButtons(button);
      var selected = toggleButton(button);
      if (selected) {
        dropDown.style.display = "block";
      } else {
        dropDown.style.display = "none";
      }
    });
    button.dropDown_ = dropDown;
    dropDownToggleButtons_.push(button);
  }

  /**
   * Resets all toggle buttons on the page.
   * param {Node} opt_exclude  A button to exclude during reset.
   */
  function resetDropDownToggleButtons(opt_exclude) {
    for (var i = 0, button; button = dropDownToggleButtons_[i]; ++i) {
      if (button.selected_ && button != opt_exclude) {
        toggleButton(button);
        button.dropDown_.style.display = 'none';
      }
    }
  }

  /**
   * Add the button to display attribution.
   */
  function addAttribution() {
    var attributions = document.getElementById('attributions');

    var size = getMapSize();
    var parentDiv = document.createElement('div');
    var button, dash, position;
    var text = 'Information Sources';
    if (size.width > 500) {
      button = createButton(text, 125);
      position = google.maps.ControlPosition.TOP_RIGHT;
      parentDiv.className = 'controlparent';
    } else {
      button = document.createElement('span');
      button.style.textDecoration = 'underline';
      button.style.color = '#7777cc';
      if (button.innerText != 'undefined') button.innerText = text;
      if (button.textContent != 'undefined') button.textContent = text;

      position = google.maps.ControlPosition.BOTTOM_LEFT;
      parentDiv.className = 'link';
    }

    google.maps.event.addDomListener(button, 'click', function() {
      attributions.style.display = 'block';
    });

    var close = document.getElementById('close');
    google.maps.event.addDomListener(close, 'click', function() {
      attributions.style.display = 'none';
    });

    parentDiv.appendChild(button);
    if (dash) parentDiv.appendChild(dash);
    map.controls[position].push(parentDiv);
  }

  function arrayContains(a, obj) {
    for (var i = 0, I = a.length; i < I; ++i) {
      if (obj == a[i]) {
        return true;
      }
    }
    return false;
  }

  /**
   * Add the button to select view.
   */
  function addViewButton(layersToShow) {
    var parentDiv = document.createElement('div');
    var button = createButton('More Layers', 105, true);

    var dropDown = document.createElement('div');
    dropDown.className = 'dropdown';
    dropDown.style.display = "none";
    dropDown.style.left = '0px';

    var views = [
      ['Rivers', 'River levels'],
      ['Echuca', 'Predicted flood levels (Echuca)'],
      ['Future', 'Predicted flood levels (Brisbane)'],
      ['B', 'Images of Bundaberg Flooding']
    ];

    for (var i = 0; i < kmlGroups.length; ++i) {
      views.push([kmlGroups[i].layers, kmlGroups[i].name]);
    }

    for (var i = 0; i < views.length; ++i) {
      var shortName = views[i][0];
      var optSet = true;

      if (shortName.length && (typeof shortName === 'object')) {
        for (var j = 0; j < shortName.length; ++j) {
          optSet &= arrayContains(layersToShow, shortName[j]) || arrayContains(layersToShow, layerMap[shortName[j]]);
        }
      } else
        optSet = arrayContains(layersToShow, shortName);

      option = createMarginOption(views[i][1], optSet);

      // Hide KML layer with this name when unchecked.
      google.maps.event.addDomListener(option, 'click', function(option, id) {
        return function() {
          var checked = option.childNodes[0].checked;

          if (typeof id !== 'object')
            id = [id];

          for (var k = 0; k < id.length; ++k) {
            if (!(id[k] in layers))
              var layer = layers[layerMap[id[k]]];
            else
              var layer = layers[id[k]];
            if (checked) {
              addLayer(id[k], layer);
            } else {
              layer.setMap(null);
            }
          }
        }
      }(option, shortName));
      dropDown.appendChild(option);
    }

    makeToggleButton(button, dropDown);

    // Append the control to the map.
    parentDiv.appendChild(button);
    parentDiv.appendChild(dropDown);
    parentDiv.className = 'controlparent';
    map.controls[google.maps.ControlPosition.TOP_RIGHT].push(parentDiv);

    // Drop down by default.
//    toggleButton(button);
//    dropDown.style.display = "block";
  }

  var events = {};
  function addLayer(index, layer) {
    // Make roads appear on top of all KML layers.
    // NOTE(jmcgill): This hammers Onion/Soprano, so removed for now.
    /*
    if (layer && layer.getMetadata) {
      events[index] = google.maps.event.addListener(
          layer, 'defaultviewport_changed', function(idx) {
            return function (e2) {
              layersToReadd = ['qldroadclosures', 'brisphotos'];

              // Re-add layers if already added.
              for (i = 0; i < layersToReadd; ++i) {
                layerId = layerMap[layersToReadd[i]];
                if (layers[layerId].setMap && layers[layerId].getMap && layers[layerId].getMap()) {
                  // Remove and re-add layer asynchronously.
                  layers[layerId].setMap(null);
                  window.setTimeout(function() {
                  layers[layerId].setMap(map); }, 0);
                }
              }
              google.maps.event.removeListener(events[idx]);
            }
          }(index));
    }
    */
    layer.setMap(map);
  }

  /**
   * Create the drop down options for displaying margins.
   * @return {Node} The node which was created.
   */
  function createMarginOption(text, checked) {
    var outerLabel = document.createElement('div');
    var input = document.createElement('input');
    var label = document.createElement('span');
    input.className = 'margininput';
    input.type = 'checkbox';
    input.checked = checked;
    label.className = 'marginlabel';
    label.innerHTML = text;
    outerLabel.className = 'marginouter';
    outerLabel.appendChild(input);
    outerLabel.appendChild(label);
    return outerLabel;
  }

  /**
   * Add a legend to the map
   */
  function addLegend() {
    var parentDiv = document.createElement('div');
    parentDiv.className = 'controlparent';

    var legend = document.createElement('div');
    legend.className = 'legend';

    var amounts = {
      "Major Flooding": "#FF0000",
      "Moderate Flooding": "#FFA938",
      "Minor Flooding": "#1C8A00",
      "Below Flood Level": "#40DDF5",
      "No Classification": "#CCCCCC"
    };

    /*
    var amounts = {
      "100+mm":"#C0262E",
      "50 to 99mm":"#0E4EBC",
      "25 to 49mm":"#FFCC33",
      "10 to 24mm":"#008856",
      ">0.2 to 9mm":"#888888",
      "0mm":"#000000"};
    */

    var textDiv = document.createElement('div');
    textDiv.innerHTML = "River Levels Legend";
    textDiv.style.fontSize = '12px';
    textDiv.style.fontWeight = 'bold';
    legend.appendChild(textDiv);

    for (var key in amounts) {
      label = createLegendLabel(key, amounts[key]);
      legend.appendChild(label);
    }

    var disclaimer = document.createElement('div');
    disclaimer.innerHTML = "Levels and locations are approximate";
    disclaimer.style.fontSize = '10px';
    disclaimer.style.color = '#555555';
    legend.appendChild(disclaimer);

    if (getMapSize().height > 400 && getMapSize().width > 350) {
      // Append the control to the map.
      // parentDiv.appendChild(button);
      parentDiv.appendChild(legend);
    } else {
      var button = createButton('Legend', 80, true);
      parentDiv.appendChild(button);

      var dropDown = document.createElement('div');
      dropDown.className = 'dropdown';
      dropDown.style.display = 'none';
      dropDown.style.right = '0px';
      dropDown.appendChild(legend);
      parentDiv.appendChild(dropDown);

      makeToggleButton(button, dropDown);
    }
    map.controls[google.maps.ControlPosition.TOP_RIGHT].push(parentDiv);
  }

  /**
   * Create a label for a single line in the legend.
   * @return {Node} The label.
   */
  function createLegendLabel(text, color) {
    var outerLabel = document.createElement('div');
    var icon = document.createElement('div');
    var label = document.createElement('span');
    icon.className = 'party';
    label.className = 'marginlabel';
    label.innerHTML = text;
    icon.className = 'party';
    icon.style.backgroundColor = color;
    outerLabel.appendChild(icon);
    outerLabel.appendChild(label);
    return outerLabel;
  }

// Extern intialize so that closure does not remove it.
window['initialize'] = initialize;
